//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <compoundChannel.h>
#include <osdepend.h>
#include <CommuChannel.h>

#include "ChannelEvent.h"
#include "ChannelMagic.h"
#include "ChannelReply.h"
#include "ChannelCommand.h"

#include "PkgManager.h"

/*
#include <_sprintf.h>

EXTERN_C void PrintComPort(const char *szMessage);

void ppcc(const char *fmt, ...)
{
    char buf[1024] = {0};
    va_list ap;
    va_start(ap, fmt);
    _vsprintf(buf, fmt, ap);
    va_end(ap);
    PrintComPort(buf);
}
*/
void ppcc(const char *fmt,...)
{
}

#if defined(_arm)
#include <mantle.h>
EXTERN_C ProcessId g_compoundChannelWriteHolderPID;
EXTERN_C ThreadId g_compoundChannelWriteHolderTID;
EXTERN_C DzEvent g_compoundChannelWriteFinsihedEvent;
#endif

static C_LOCK s_LockOfSendPackage;

static unsigned s_CurrentPackageNumber = 0;

#define RECENT_PACKAGE_COUNT 20

static unsigned int s_RecentPackages[RECENT_PACKAGE_COUNT] = {0};

EXTERN_C C_INT CreatePkgManager()
{
    if (0 != ChannelInitPort()) {
        COMMU_PRINTF("{Err}: InitPort failed! file%s. line:%d.\n",
            __FILE__, __LINE__);
        return C_FAIL;
    }

    s_LockOfSendPackage = InitLock();
    if (NULL == s_LockOfSendPackage) {
        COMMU_PRINTF("ERR: Init Lock fail! file%s. line:%d.\n",
                    __FILE__, __LINE__);
        return C_OUT_OF_MEMORY;
    }

    if (0 != CreateChannelEventStorage()) {
        COMMU_PRINTF("{Err}: CreateChannelEventStorage failed! file%s. line:%d.\n",
            __FILE__, __LINE__);
        return C_FAIL;
    }

    return 0;
}

EXTERN_C C_VOID DelPkgManager()
{
    DelChannelEventStorage();
    DeleLock(s_LockOfSendPackage);
}

EXTERN_C C_INT SendChannelData(ChannelData *data)
{
    return SendOutChannelData(data, true);
}

EXTERN_C C_INT SendOutChannelData(ChannelData *data, bool bBlock)
{
    if (NULL == data) {
        return C_INVALID_ARGUMENT;
    }
    C_UINT channel_number = data->GetChannelNumber();

    C_INT ret;
    WaitReply* pWaitReply = NULL;

    assert(channel_number < 0x10000);// channel_number has 2BYTES valid

#if defined(_arm)
    ProcessId curPID = 0;
    CProcess *pProc = ::GetCurrentProcess();
    if (pProc != NULL) {
        pProc->GetId(&curPID);
    }
    ThreadId curTID = 0;
    Thread* pThd = ::GetCurrentThread();
    if (pThd != NULL) {
        pThd->GetId(&curTID);
    }
#endif

    Lock(s_LockOfSendPackage);

#if defined(_arm)
    g_compoundChannelWriteHolderPID = curPID;
    g_compoundChannelWriteHolderTID = curTID;
#endif

    if (CreateWaitReply(channel_number, &pWaitReply)) {
        COMMU_PRINTF("{Err}: CreateWaitReply failed! file%s. line:%d. channel_number:%d.\n",
                __FILE__, __LINE__, channel_number);
        UnLock(s_LockOfSendPackage);

#if defined(_arm)
        g_compoundChannelWriteHolderPID = 0;
        g_compoundChannelWriteFinsihedEvent.Notify();
#endif

        return C_OUT_OF_MEMORY;
    }

    pWaitReply->bTransSucceed = false;
    C_INT tries = 3;
    do {
        ret = data->Write(ChannelSendData);
        if (0 != ret) {
            COMMU_PRINTF("{Err}: SendData failed! file%s. line:%d. channel_number:%d.\n",
                __FILE__, __LINE__, channel_number);
            break;
        }
        // another incoming data may disturb outgoing package (occupying channel)
        // console input/output may disturb outgoing package (occupying channel)
        // reply package may be lost according to above senarios.
        ret = WaitForEvent(pWaitReply->event, TIMEOUT);
        if (0 != ret) {
            COMMU_PRINTF("{Err}: SendData time out! file%s. line:%d. channel_number:%d.\n",
                __FILE__, __LINE__, channel_number);
            if (C_TIME_OUT != ret) {
                break;
            }
            tries--;
            if (!bBlock && tries <= 0) {
                break;
            }
            else if (bBlock && tries * TIMEOUT < -15000) { // 15 seconds' timeout
                break;
            }
            if (channel_number != 0) {
                CommunicationObject *pObj = FindObjByChannelNumber(&CommunicationObject::m_sRunningList,
                        channel_number,
                        CommunicationObject::m_sRunnListLock);
                if (NULL == pObj || pObj->m_status != running) {
                    COMMU_PRINTF("{Err}: Channel %d is destroyed. file%s. line:%d. channel_number:%d.\n",
                        __FILE__, __LINE__, channel_number);
                    break;
                }
                if (pObj->m_property == console) {
                    COMMU_PRINTF("{Warn}: Console printf tries %d!.\r\n", tries);
                    if (tries <= 0) {
                        COMMU_PRINTF("{Err}: Break for console!.\r\n");
                        break;
                    }
                }
            }
        }
    } while (!pWaitReply->bTransSucceed);

    ReleaseWaitReply(pWaitReply);

#if defined(_arm)
    g_compoundChannelWriteHolderPID = 0;
#endif

    UnLock(s_LockOfSendPackage);

#if defined(_arm)
    g_compoundChannelWriteFinsihedEvent.Notify();
#endif

    return ret;
}

EXTERN_C ChannelData* ReceiveChannelData(int *err)
{
    C_INT ret = 0;

    ChannelMagic cm;
    do {
        ret = cm.Read(ChannelReceiveData);
        if (ret != CHANNEL_OK) {
            COMMU_PRINTF("{Err}: ReceiveMagicNumber failed! file%s. line:%d.\n",
                __FILE__, __LINE__);
            *err = CHANNEL_IO_ERROR;
            return NULL; // error! return directly
        }
        if (ChannelMagic::TYPE_SEND != cm.GetNumber()) {
            // a reply
            ChannelReply cr;
            ret = cr.Read(ChannelReceiveData);
            if (ret == CHANNEL_OK) {
                ret = NotifyChannelEvent(cr.GetChannelNumber(),
                        ChannelMagic::TYPE_SUCCEED == cm.GetNumber());
                if (ret != 0) { // error
                    COMMU_PRINTF("{Err}: Notify %d channel for package %d with %d event failed! file%s. line:%d.\n",
                        cr.GetChannelNumber(), cr.GetPackageNumber(), cm.GetNumber(), __FILE__, __LINE__);
                }
            }
            else {
                // should be re-notified!
                if (ret == CHANNEL_CRC_ERROR) {
                    COMMU_PRINTF("{Err}: Reply %d CRC checking failed! file%s. line:%d.\n",
                        cr.GetPackageNumber(), __FILE__, __LINE__);
                }
                COMMU_PRINTF("{Err}: Lost a reply failed! file%s. line:%d.\n",
                    __FILE__, __LINE__);
            }
        }
    } while (ChannelMagic::TYPE_SEND != cm.GetNumber()); // read until there is a data package

    // read data package
    ChannelData *cd = new ChannelData();
    if (cd == NULL) { // Out of memory
        *err = CHANNEL_OUT_OF_MEMORY;
        return NULL;
    }
    ret = cd->Read(ChannelReceiveData);
    COMMU_PRINTF("{Info}: Recv Data %d.\r\n", cd->GetPackageNumber());
    if (ret != CHANNEL_OK) {
        if (ret == CHANNEL_CRC_ERROR) {
            ChannelReply cr(cd->GetChannelNumber(), cd->GetPackageNumber(), ChannelMagic::TYPE_FAIL);
            cr.Write(ChannelSendData);
            COMMU_PRINTF("{Info}: CRC Reply %d.\r\n", cr.GetPackageNumber());
            COMMU_PRINTF("{Err}: CRC Check failed! file%s. line:%d.\n",
                __FILE__, __LINE__);
        }
        else {
            COMMU_PRINTF("{Err}: Header failed! file%s. line:%d.\n",
                __FILE__, __LINE__);
        }
        delete cd;
        return NULL; // error!
    }
    if (s_CurrentPackageNumber >= cd->GetPackageNumber()) {
        // send reply again!
        ChannelReply cr(cd->GetChannelNumber(), cd->GetPackageNumber(), ChannelMagic::TYPE_SUCCEED);
        cr.Write(ChannelSendData);
        for (int i = 0; i < RECENT_PACKAGE_COUNT; i++) {
            if (s_RecentPackages[i] == cd->GetPackageNumber()) {
                COMMU_PRINTF("{Info}: Discard package %d.\r\n", cd->GetPackageNumber());
                delete cd;
                return NULL;
            }
        }
    }
    s_CurrentPackageNumber = cd->GetPackageNumber();
    for (int i = RECENT_PACKAGE_COUNT - 1; i >= 1; i--) {
        s_RecentPackages[i] = s_RecentPackages[i - 1];
    }
    s_RecentPackages[0] = s_CurrentPackageNumber;

    // send a reply
    ChannelReply cr(cd->GetChannelNumber(), cd->GetPackageNumber(), ChannelMagic::TYPE_SUCCEED);
    cr.Write(ChannelSendData);
    if (cd->GetChannelNumber() == 0) {
        COMMU_PRINTF("{Info}: Reply %d Command.\r\n", cr.GetPackageNumber());
        ChannelCommand *cc = new ChannelCommand(cd); // convert to ChannelCommand
        delete cd;
        return cc;
    }
    else {
        COMMU_PRINTF("{Info}: Reply %d Data.\r\n", cr.GetPackageNumber());
        return cd;
    }
}
